﻿//#define _CRT_SECURE_NO_WARNINGS
//#include <stdio.h>
//#include <math.h>

#include "common.h"

/*
C语言的数据存储都是二进制的，整数的二进制表示有三种：补码、原码、反码。
	补码就是符号位加上真值的绝对值（补码就是整数在内存中的表示），
	原码就是符号位加上真值的原值，
	反码就是符号位取反加上真值的绝对值。
	正整数的补码、原码、反码都是一样的，比如 +7 的原码、反码、补码分别是
	原码：00000000 00000000 00000000 00000111   ————> 4字节
	反码：00000000 00000000 00000000 00000111
	补码：00000000 00000000 00000000 00000111

	负整数的补码、原码、反码需要计算，比如 -7 的原码、反码、补码分别是
	原码：10000000 00000000 00000000 00000111   ————> 4字节
	反码：11111111 11111111 11111111 11111000
		（原码的符号位不变，其余位按位取反）
	补码：11111111 11111111 11111111 11111001（反码加一）

	说明：第一个0是符号位（最高位），表示正数；1表示负数。

运算符的优先级：
1. 赋值运算符：=
2. 位运算符：&、|、^、~、<<、>>
3. 条件运算符：? :
4. 比较运算符：==、!=、<、<=、>、>=
5. 逻辑运算符：!、&&、||
6. 算术运算符：+、-、*、/、%
7. 自增、自减运算符：++、--
8. 前缀运算符：&、*、+、-、!、~、++、--、
	sizeof、(type)、(expr)、(cast)
9. 下标引用运算符：[]
10. 成员运算符：->、.
11. 调用运算符：()
越往后优先级越高，相同优先级的运算符从左到右进行运算。
复合赋值运算符：+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=

运算符的注意事项：
1. 除法运算符/的优先级比其他运算符低，所以在表达式中使用时，需要加括号。
2. 位运算符的操作对象是整数，运算结果也是整数。
3. 赋值运算符的右侧表达式的值会被计算并赋值给左侧变量。
4. 条件运算符的条件表达式的值决定了选择哪个值作为结果。
	即 表达式1? 表达式2 : 表达式3 如果表达式1的值为真（非0），则选择
	表达式2作为结果；否则选择表达式3作为结果。
5. 逻辑运算符的操作对象是布尔值，运算结果也是布尔值。
6. 位运算符的操作对象是整数，运算结果也是整数。
7. 算术运算符的操作对象是整数或浮点数，运算结果也是整数或浮点数。
8. 自增、自减运算符的操作对象是变量，运算结果也是变量。
9. 成员运算符的操作对象是结构体、类、共用体等，运算结果是成员的值。
10. 调用运算符的操作对象是函数，运算结果是函数的返回值。
11. 后缀运算符的操作对象是数组、结构体、类、共用体等，
	运算结果是操作对象的值。
12. 前缀运算符的操作对象是变量、函数、括号表达式、sizeof、类型名、
	类型关键字等，运算结果是操作对象的值。

整型运算规则：C的整型运算时会自动进行类型提升，
	即把小整型转换为至少达到普通整型（int）的大小才进行运算。
	long long = 64比特(bite)，long、int = 32比特，short = 16比特，char = 8比特。
	CPU通用寄存器的大小为普通整型的大小，运算结束后，结果也会被截断，
	然后转换为目标类型（int）然后储存。
*/

void test_sizeof1(char ch[])
{
	printf("sizeof(ch) = %zd\n", sizeof(ch));
	// 8，因为数组首元素的指针占1个字节（64位机器）
}

void test_sizeof2(char* ch)
{
	printf("sizeof(ch) = %zd\n", sizeof(ch));
	// 8，因为指针（也是数组首元素地址）占8个字节（64位机器）
}

int main()
{
	//数据类型内存占用大小
	{
		print_start_line("数据类型内存占用大小测试开始");
		printf("char = %zd\n", sizeof(char)); // 1
		printf("short = %zd\n", sizeof(short)); // 2
		printf("int = %zd\n", sizeof(int)); // 4
		printf("unsigned int = %zd\n", sizeof(unsigned int)); // 4
		printf("long = %zd\n", sizeof(long)); // 4
		printf("long long = %zd\n", sizeof(long long)); // 8
		printf("float = %zd\n", sizeof(float)); // 4
		printf("double = %zd\n", sizeof(double)); // 8
		printf("long double = %zd\n", sizeof(long double)); // 16
		printf("void* = %zd\n", sizeof(void*)); // 8（64位机器）
		printf("void** = %zd\n", sizeof(void**)); // 8（64位机器）

		print_end_line("数据类型内存占用大小测试完毕"); // 10
	}

	//位操作符部分
	{
		print_start_line("位操作符测试开始");
		// 移位操作符（只针对整数，移动正整数位）：<<、>>
		int a1 = 7;
		int b1 = a1 << 2; // b1 = 28 左移2位，补码左边丢弃，右边补0
		/* 相当于：00000000 00000000 00000000 00000111 << 2 =
				  00000000 00000000 00000000 00011100  ----> 28 */
		printf("a1 = %d, b1 = %d\n", a1, b1);
		int a2 = -7;
		int b2 = a2 << 2; // b2 = -28 左移2位，补码左边补1，右边补0
		/* 相当于：11111111 11111111 11111111 11111001 << 2 =
			11111111 11111111 11111111 11100100 ----> 这是补码
			11111111 11111111 11111111 11100011 ----> 这是反码
			10000000 00000000 00000000 00011100	----> 这是原码：-28 */
		printf("a2 = %d, b2 = %d\n", a2, b2);
		// 总结：左移 n(n<31) 位，相当于乘以 2 的 n 次方。

		// 右移运算符分两种情况：
		// 1.逻辑右移：右边丢弃，左边补 0（一般没有用）
		// 2.算术右移：右边补符号位，左边符号位补原符号，其他位补原来的数值
		int a3 = 7;
		int b3 = a3 >> 2; // b3 = 0 右移2位，算数右移，右边丢弃，左边补0
		/* 00000000 00000000 00000000 00000111 >> 2 =
		   00000000 00000000 00000000 00000001   ----> 1 */
		printf("a3 = %d, b3 = %d\n", a3, b3);
		int a4 = -7;
		int b4 = a4 >> 2; // b4 = -1 右移2位，算术右移
		//int c4 = a4 >> -2;//移动位数为负，C语言标准未定义型，结果未知
		/* 相当于：11111111 11111111 11111111 11111001 >> 2 =
			11111111 11111111 11111111 11111110  ----> 这是补码
			11111111 11111111 11111111 11111101 ----> 这是反码
			00000000 00000000 00000000 00000010  ----> 这是原码：-2 */
		printf("a4 = %d, b4 = %d\n", a4, b4);
		int a5 = 1;
		int b5 = a5 << 31; // 注意首位代表符号
		printf("a5 = %d, b5 = %d，2^31 = %d\n", a5, b5,
			(unsigned int)pow(2, 31));

		// 位运算符：&、|、^、~
		int a6 = 7;
		int b6 = -5;
		int c6 = a6 & b6; // 按位与（同为1时为1，否则为0）
		/* 计算过程：
			00000000 00000000 00000000 00000111 &   这是 7 的补码
			11111111 11111111 11111111 11111110 =   这是 -5 的补码
			00000000 00000000 00000000 00000110     这是结果补码
			00000000 00000000 00000000 00000011		这是结果反码（正数）
			00000000 00000000 00000000 00000011     这是结果原码 */
		printf("a6 = %d, b6 = %d, c6 = %d\n", a6, b6, c6);

		int a7 = 7;
		int b7 = -5;
		int c7 = a7 | b7; // 按位或（同为0时为0，否则为1）
		printf("a7 = %d, b7 = %d, c7 = %d\n", a7, b7, c7); // c7 = -1

		int a8 = 7;
		int b8 = -5;
		int c8 = a8 ^ b8; // 按位异或（不同时为1，相同时为0），支持交换律
		printf("a8 = %d, b8 = %d, c8 = %d\n", a8, b8, c8); // c8 = -4

		// 题目一：不创建临时变量交换两个整型变量的值
		int x = 10, y = 20;
		x = x + y; // 如果 x + y 的值溢出了，结果可能是不正确的
		y = x - y;
		x = x - y;
		printf("x = %d, y = %d\n", x, y); // x = 20, y = 10
		x = x ^ y;
		y = x ^ y;
		x = x ^ y;
		printf("x = %d, y = %d\n", x, y); // x = 10, y = 20

		// 题目二：求一个整数的在内存中二进制（补码）的 1 的个数
		int num1 = -15;
		/* 原码：10000000 00000000 00000000 00001111
		   反码：01111111 11111111 11111111 11110000
		   补码：01111111 11111111 11111111 11110001  28个1 */
		printf("二进制 num1 中有%d个1\n", binary_one_count(num1));

		// 按位取反运算符 ~ 也是一个单目操作符，但它的操作对象是整数
		int a9 = -7;
		int b9 = ~a9; // 按位取反，即取反每一位（0变1，1变0）
		/* 计算过程：
			11111111 11111111 11111111 11111001   这是 -7 的补码
			00000000 00000000 00000000 00000110   这结果的补码 */
		printf("a9 = %d, b9 = %d\n", a9, b9);

		print_end_line("位操作符测试完毕");
	}

	// 赋值操作符 =
	{
		print_start_line("赋值操作符测试开始");
		int m1 = 10;
		int m2 = 20;
		m1 = m2 = m1 + m2;// 先计算 m1 + m2 的值，再赋值给 m1 和 m2
		//不推荐以上写法，容易造成混淆，且不方便调试
		printf("m1 = %d, m2 = %d\n", m1, m2);// m1 = 30, m2 = 30

		print_end_line("赋值操作符测试完毕");
	}

	// 单目操作符 +、-、!、~、++、--、sizeof、(type)
	{ // +、- 表示正负
		print_start_line("单目操作符测试开始");
		//if (!flag) {} 表示 flag 为 false 时执行 {} 块
		//printf("%d\n", 1 << 0); // 1 可以左移、右移 0 位
		// 单目操作符值意为值操作一个值（或类型）的运算符
		printf("改变%d的二进制数右边第%d为的值为%d\n",
			-13, 5, change_bit(-13, 5));
		int i1 = 3;
		int j1 = --i1; // 先 -- 再赋值
		printf("a = %d, b = %d\n", i1, j1); // a = 2, b = 2
		int i2 = 3;
		int j2 = i2++; // 先赋值再 ++
		printf("a2 = %d, b3 = %d\n", i2, j2); // a2 = 4, b2 = 3
		//printf("++%d = %d\n", 10, (++10)); // 先 ++ 再输出，报错
		//printf("%d++ = %d\n", 10, 10--); // 先输出再 ++ ，报错

		// 证明 sizeof 是单目操作符
		int i3 = 10;
		printf("sizeof a3 = %d\n", (int)(sizeof i3)); // ok
		printf("sizeof 10 = %d\n", (int)(sizeof 10)); // ok
		//printf("sizeof(10) = %d\n", sizeof int); // err
		char ch[12] = "hello, world";
		printf("sizeof ch = %d\n", (int)(sizeof ch)); // ok
		test_sizeof1(ch); // 调用函数，传入数组名，返回数组的大小
		test_sizeof2(ch); // 调用函数，传入数组名，返回数组的大小

		print_end_line("单目操作符测试完毕");
	}

	// 比较操作符 ==、!=、<、<=、>、>=
	{
		print_start_line("比较操作符测试开始");
		char arr1[] = "hello";
		char arr1_copy[] = "hello";
		char arr2[] = "haha";
		char arr3[] = "hehe_hehe";
		if (arr1 == arr1_copy)
			printf("arr1 == arr1_copy\n");
		if (arr1 == arr2 && arr2 == arr3)
			// 数组比较，比较的是首元素地址
			printf("arr1 == arr2 == arr3\n");
		if ("hello" == "hahahaha")
			printf("字符串比较成功\n");
		// 上面三个 if 均不会输出，
		// 因为每个字符串的首元素地址在内存中是不同的

		print_end_line("比较操作符测试完毕");
	}

	// 逻辑操作符 &&、||、!
	{
		print_start_line("逻辑操作符测试开始");
		int jl = 0, al = 10, bl = 20, cl = 30;
		if (al > 0 && bl > 0 && cl > 0)
			printf("a, b, c 都大于 0\n"); // true
		/* 对于&& 运算符，从左往右依次计算，
			如果左边为 false，则右边不计算 */
		int il = jl++ && al++ && ++bl && cl++;
		// jl 先 && 再 ++ 因为 jl++ 先使用 && 运算，然后 ++ 运算
		printf("il = %d, jl = %d, al = %d, bl = %d, cl = %d\n",
			il, jl, al, bl, cl);
		// il = 1, jl = 1, al = 10, bl = 20, cl = 30
		/* 对于 || 运算符，从左往右依次计算，
			如果左边为 true，则右边不计算 */
		int jl2 = 0, al2 = 10, bl2 = 20, cl2 = 30;
		int il2 = jl2++ || ++al2 || bl2++ || cl2++;
		printf("il2 = %d, jl2 = %d, al2 = %d, bl2 = %d, cl2 = %d\n",
			il2, jl2, al2, bl2, cl2);
		// il2 = 1, jl2 = 1, al2 = 11, bl2 = 20, cl2 = 30
		// 对于 ! 运算符，取反操作，true 变 false，false 变 true
		int flag = 0;
		if (!flag)
			printf("flag 为 false，!flag 为 true\n"); // 会执行

		print_end_line("逻辑操作符测试完毕");
	}

	// 逗号表达式
	{ // 逗号表达式，从左往右计算，返回最后一个表达式的值
		print_start_line("逗号表达式测试开始");
		// 语法：(expr1, expr2, expr3,..., exprn) 返回 exprn
		int ad = 10, bd = 20, cd = 30;
		int dh = (ad++, bd++, ++cd, ad + bd + cd);
		printf("dh = %d, ad = %d, bd = %d, cd = %d\n",
			dh, ad, bd, cd);//dh = 63, ad = 11, bd = 21, cd = 31

		// 交换 ad 和 bd 的值
		ad = (ad = ad + bd, bd = ad - bd, ad - bd);
		printf("ad = %d, bd = %d\n", ad, bd);//ad = 21, bd = 11

		print_end_line("逗号表达式测试完毕");
	}

	// 下标引用操作符、成员访问操作符与函数调用操作符 []、.、->、()
	{
		print_start_line("下标引用操作符、成员访问操作符与函数调用操作符测试开始");
		// 数组下标引用操作符 [] 支持交换律
		int arr_char[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
		//这里注意定义数组的时候不支持交换律，int 10[arr] 报错

		7[arr_char] = 100; // 数组下标引用操作符，修改数组元素
		printf("arr_char[7] = %d\n", arr_char[7]);

		// 结构体成员访问操作符 . 与结构体指针访问操作符 ->
		student_test();

		print_end_line("下标引用操作符、成员访问操作符与函数调用操作符测试完毕");
	}

	//表达式求值
	{
		print_start_line("表达式求值测试开始");
		char c0 = -1; // char 类型的变量存储 -128 到 127 之间的整数
		printf("sizeof c0 = %zd\n", sizeof c0); // 1
		/* c1 在内存中的存储如下（从后面截取对应大小的位数）：
		10000000 00000000 00000000 00000001  -1 的原码
		11111111 11111111 11111111 11111110  -1 的反码
		11111111 11111111 11111111 11111111  -1 的补码
								   11111111  c0 的实际存储 */
								   // 在实际计算的过程中，c0 首先会被转化为 int 类型，然后进行运算
								   /* 即整型提升为 - 1 的补码，具体为：
									   最高位(c0 的左边第一位)会被当做高位补全提升的值进行补全，
									   无符号的高位全部补全为 0 */
		char c1 = 5;
		char c2 = 126;
		char c3 = c1 + c2;
		printf("c2 = %d\n", c3); // c2 = -125
		/* 计算过程：char 类型的数值会被转化为 int 类型进行运算，
		000000101   ---->  c1 的补码
		011111110   ---->  c2 的补码
		00000000 00000000 00000000 00000101  c1 整型提升
		00000000 00000000 00000000 01111110  c2 整型提升
		00000000 00000000 00000000 10000011  结果的补码
								   10000011  c3 的实际存储
		11111111 11111111 11111111 10000011  c3 的整型提升（补码）
		11111111 11111111 11111111 10000010  c3 整型提升的反码
		10000000 00000000 00000000 01111101  c3 整型提升的原码 --> -125 */

		char c4 = 0x66;  // 01100110（没有超过 8 位，且最高位是 0 与原数相同）
		unsigned char c4_unsigned = -0x66;
		/* c4_unsigned 在内存中的存储如下（注意最高位强制转换为 0）：
		10000000 00000000 00000000 01100110  -0x66 的原码
		11111111 11111111 11111111 10011001  -0x66 的反码
		11111111 11111111 11111111 10011010  -0x66 的补码
								   10011010  c4_unsigned 的实际存储
		if 语句进行比较时，会先将 char 转化为 int 类型，然后进行比较
		00000000 00000000 00000000 10011010  c4_unsigned 的整型提升的补码
		00000000 00000000 00000000 10011010  c4_unsigned 的整型提升的原码
		因为 unsigned 修饰 char 变量，整型提升是无符号的，也就是高位补 0 */
		//printf("c4_unsigned = %d\n", c4_unsigned);  // 154
		char c4_over = 0x86; // 注意最高位是 1
		/* c4_over 在内存中的存储如下：
		00000000 00000000 00000000 10000110  0x86 的原码
								   10000110  0x86 的实际存储
		11111111 11111111 11111111 10000110  0x86 的整型提升(补码)
		10000000 00000000 00000000 10000101  0x86 整型提升的反码
		10000000 00000000 00000000 01111010  0x86 整型提升的原码 -122 */
		//printf("c4_over = %d\n", c4_over); // -122
		short c5 = 0x3f000; // 11110000 00000000（超范围，会截断）
		// 00000011 11110000 00000000  -->  11110000 00000000 (截断)
		int c6 = 0xfff0000;
		if (0x66 == c4) // true
			printf("c4 = 0x66\n");
		if (-0x66 == c4_unsigned) // false
			// 这里 c4_unsigned 的整型提升（154）不等于 -0x66，所以 false
			printf("c4_sign = -0x66\n");
		if (0x86 == c4_over) // false
			// 这里 c4_over 的整型提升（-122）不等于 0x86，所以 false
			printf("c4_over = 0x86\n");
		if (0x3f000 == c5) // false
			printf("c5 = 0x3f000\n");
		if (0xfff0000 == c6) // true
			printf("c6 = 0xfff0000\n");

		char c7 = 5;
		printf("sizeof c7 = %zd\n", sizeof c7); // 1
		printf("sizeof -c7 = %zd\n", sizeof - c7); // 4，整型提升
		printf("sizeof +c7 = %zd\n", sizeof + c7); // 4，整型提升

		/* 同为整型数值，计算时会往大的类型提升，比如 int + float 结果为 float
			int 会被转换位 float 类型，然后进行运算 */

		print_end_line("表达式求值测试完毕");
	}

	// 作业讲解部分
	{
		print_start_line("作业讲解部分开始");
		// 1. 走台阶问题：
		/* 一只青蛙一次可以跳上 1 级台阶，也可以跳上 2 级台阶。
			求该青蛙跳上一个 n 级的台阶总共有多少种跳法。*/
		printf("一次只能上一个或两个台阶，走%d级台阶总共有%d种走法\n",
			5, up_stairs_all(5));
		// 2. 自己输入一个整型数组，删除一个查找值，打印删除后数组（不改变顺序）
		//show_pop_after_array();

		// 3. 自己输入一个整型数组，打印最大值和最小值的差值
		//show_max_min_diff();

		//4. 大小写转换：输入一个字符（字母），大写转小写，小写转大写
		//up_low_reverse();

		//5.判断 lily number 即一个数从中间任意位置拆分成 n 组两个数
		// 使得这些组的两个数的乘积的求和等于该数本身
		printf_five_lily_number();

		print_end_line("作业讲解部分结束");
	}

	return 0;
}

